// Parts Copyright 2008 Newgrove Consultants Limited
// Modified By John Diss 
// From original work:
//
// Copyright 2005, 2006 - Morten Nielsen (www.iter.dk)
//
// This file is part of SharpMap.
// SharpMap is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// SharpMap is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public License
// along with SharpMap; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 

using System;
using System.Collections.Generic;
using GeoAPI.Coordinates;
using GeoAPI.CoordinateSystems.Transformations;
using GeoAPI.DataStructures;
using GeoAPI.Geometries;
using NPack.Interfaces;

namespace MsSqlSpatial
{
    /// <summary>
    /// Helper class for transforming <see cref="Geometry"/>
    /// </summary>
    public class GeometryTransform
        //where ICoordinate : ICoordinate, IEquatable, IComparable,
        //            IComputable<Double, ICoordinate>, IConvertible
    {
        /// <summary>
        /// Transforms a <see cref="Envelope"/>.
        /// </summary>
        /// <param name="box">BoundingBox to transform</param>
        /// <param name="transform">Math Transform</param>
        /// <returns>Transformed object</returns>
        public static IExtents TransformBox(IExtents box, IMathTransform transform, IGeometryFactory geometryFactory)
        {
            if (box == null)
                return null;

            var corners = new ICoordinate[2];
            corners[0] = transform.Transform(box.Min);
            corners[1] = transform.Transform(box.Max);

            IExtents result = geometryFactory.CreateExtents();

            foreach (ICoordinate p in corners)
                result.ExpandToInclude(p[Ordinates.X], p[Ordinates.Y]);
            return result;
        }

        public static IExtents<TCoordinate> TransformBox<TCoordinate>(IExtents<TCoordinate> box,
                                                                      IMathTransform<TCoordinate> transform,
                                                                      IGeometryFactory<TCoordinate> geometryFactory)
            where TCoordinate : ICoordinate<TCoordinate>, IEquatable<TCoordinate>, IComparable<TCoordinate>,
                IComputable<Double, TCoordinate>, IConvertible
        {
            return (IExtents<TCoordinate>) TransformBox(box, transform, (IGeometryFactory) geometryFactory);
        }


        /// <summary>
        /// Transforms a <see cref="Geometry"/>.
        /// </summary>
        /// <param name="g">Geometry to transform</param>
        /// <param name="transform">MathTransform</param>
        /// <returns>Transformed Geometry</returns>
        public static IGeometry TransformGeometry(IGeometry g, IMathTransform transform,
                                                  IGeometryFactory geometryFactory)
        {
            if (g == null)
                return null;
            else if (g is IPoint)
                return TransformPoint(g as IPoint, transform, geometryFactory);
            else if (g is ILineString)
                return TransformLineString(g as ILineString, transform, geometryFactory);
            else if (g is IPolygon)
                return TransformPolygon(g as IPolygon, transform, geometryFactory);
            else if (g is IMultiPoint)
                return TransformMultiPoint(g as IMultiPoint, transform, geometryFactory);
            else if (g is IMultiLineString)
                return TransformMultiLineString(g as IMultiLineString, transform, geometryFactory);
            else if (g is IMultiPolygon)
                return TransformMultiPolygon(g as IMultiPolygon, transform, geometryFactory);
            else throw new ArgumentException("Could not transform geometry type '" + g.GetType() + "'");
        }

        public static IGeometry<TCoordinate> TransformGeometry<TCoordinate>(IGeometry<TCoordinate> g,
                                                                            IMathTransform<TCoordinate> transform,
                                                                            IGeometryFactory<TCoordinate>
                                                                                geometryFactory)
            where TCoordinate : ICoordinate<TCoordinate>, IEquatable<TCoordinate>, IComparable<TCoordinate>,
                IComputable<Double, TCoordinate>, IConvertible
        {
            return (IGeometry<TCoordinate>) TransformGeometry(g, transform, (IGeometryFactory) geometryFactory);
        }


        /// <summary>
        /// Transforms a <see cref="Point"/>.
        /// </summary>
        /// <param name="p">Point to transform</param>
        /// <param name="transform">MathTransform</param>
        /// <returns>Transformed Point</returns>
        public static IPoint TransformPoint(IPoint p, IMathTransform transform, IGeometryFactory geometryFactory)
        {
            try
            {
                return geometryFactory.CreatePoint(transform.Transform(p.Coordinate));
            }
            catch
            {
                return null;
            }
        }

        public static IPoint<TCoordinate> TransformPoint<TCoordinate>(IPoint<TCoordinate> p,
                                                                      IMathTransform<TCoordinate> transform,
                                                                      IGeometryFactory<TCoordinate> geometryFactory)
            where TCoordinate : ICoordinate<TCoordinate>, IEquatable<TCoordinate>, IComparable<TCoordinate>,
                IComputable<Double, TCoordinate>, IConvertible
        {
            return (IPoint<TCoordinate>) TransformPoint(p, transform, (IGeometryFactory) geometryFactory);
        }

        /// <summary>
        /// Transforms a <see cref="LineString"/>.
        /// </summary>
        /// <param name="l">LineString to transform</param>
        /// <param name="transform">MathTransform</param>
        /// <returns>Transformed LineString</returns>
        public static ILineString TransformLineString(ILineString l, IMathTransform transform,
                                                      IGeometryFactory geometryFactory)
        {
            try
            {
                return geometryFactory.CreateLineString(ExtractCoordinates(l, transform));
            }
            catch
            {
                return null;
            }
        }

        public static ILineString<TCoordinate> TransformLineString<TCoordinate>(ILineString<TCoordinate> l,
                                                                                IMathTransform<TCoordinate> transform,
                                                                                IGeometryFactory<TCoordinate>
                                                                                    geometryFactory)
            where TCoordinate : ICoordinate<TCoordinate>, IEquatable<TCoordinate>, IComparable<TCoordinate>,
                IComputable<Double, TCoordinate>, IConvertible
        {
            return (ILineString<TCoordinate>) TransformLineString(l, transform, (IGeometryFactory) geometryFactory);
        }

        /// <summary>
        /// Transforms a <see cref="LinearRing"/>.
        /// </summary>
        /// <param name="r">LinearRing to transform</param>
        /// <param name="transform">MathTransform</param>
        /// <returns>Transformed LinearRing</returns>
        public static ILinearRing TransformLinearRing(ILinearRing r, IMathTransform transform,
                                                      IGeometryFactory geometryFactory)
        {
            try
            {
                return geometryFactory.CreateLinearRing(ExtractCoordinates(r, transform));
            }
            catch
            {
                return null;
            }
        }

        public static ILinearRing<TCoordinate> TransformLinearRing<TCoordinate>(ILinearRing<TCoordinate> r,
                                                                                IMathTransform<TCoordinate> transform,
                                                                                IGeometryFactory<TCoordinate>
                                                                                    geometryFactory)
            where TCoordinate : ICoordinate<TCoordinate>, IEquatable<TCoordinate>, IComparable<TCoordinate>,
                IComputable<Double, TCoordinate>, IConvertible
        {
            return (ILinearRing<TCoordinate>) TransformLinearRing(r, transform, (IGeometryFactory) geometryFactory);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ls"></param>
        /// <param name="transform"></param>
        /// <returns></returns>
        private static IEnumerable<ICoordinate> ExtractCoordinates(ILineString ls, IMathTransform transform)
        {
            foreach (ICoordinate c in ls.Coordinates)
                yield return transform.Transform(c);
        }


        /// <summary>
        /// Transforms a <see cref="Polygon"/>.
        /// </summary>
        /// <param name="p">Polygon to transform</param>
        /// <param name="transform">MathTransform</param>
        /// <returns>Transformed Polygon</returns>
        public static IPolygon TransformPolygon(IPolygon p, IMathTransform transform, IGeometryFactory geometryFactory)
        {
            return geometryFactory.CreatePolygon(
                TransformLinearRing((ILinearRing) p.ExteriorRing, transform, geometryFactory),
                Processor.Transform(
                    p.InteriorRings,
                    o => TransformLinearRing((ILinearRing) o, transform, geometryFactory)));
        }


        /// <summary>
        /// Transforms a <see cref="MultiPoint"/>.
        /// </summary>
        /// <param name="points">MultiPoint to transform</param>
        /// <param name="transform">MathTransform</param>
        /// <returns>Transformed MultiPoint</returns>
        public static IMultiPoint TransformMultiPoint(IMultiPoint points, IMathTransform transform,
                                                      IGeometryFactory geometryFactory)
        {
            return
                geometryFactory.CreateMultiPoint(Processor.Transform((IEnumerable<IPoint>) points,
                                                                      o => transform.Transform(o.Coordinate)));
        }


        /// <summary>
        /// Transforms a <see cref="MultiLineString"/>.
        /// </summary>
        /// <param name="lines">MultiLineString to transform</param>
        /// <param name="transform">MathTransform</param>
        /// <returns>Transformed MultiLineString</returns>
        public static IMultiLineString TransformMultiLineString(IMultiLineString lines, IMathTransform transform,
                                                                IGeometryFactory geometryFactory)
        {
            return geometryFactory.CreateMultiLineString(Processor.Transform<ILineString>(lines,
                                                                                           o =>
                                                                                           TransformLineString(o,
                                                                                                               transform,
                                                                                                               geometryFactory)));
        }

        /// <summary>
        /// Transforms a <see cref="MultiPolygon"/>.
        /// </summary>
        /// <param name="polys">MultiPolygon to transform</param>
        /// <param name="transform">MathTransform</param>
        /// <returns>Transformed MultiPolygon</returns>
        public static IMultiPolygon TransformMultiPolygon(IMultiPolygon polys, IMathTransform transform,
                                                          IGeometryFactory geometryFactory)
        {
            return geometryFactory.CreateMultiPolygon(Processor.Transform<IPolygon>(polys,
                                                                                     o =>
                                                                                     TransformPolygon(o, transform,
                                                                                                      geometryFactory)));
        }


        /// <summary>
        /// Transforms a <see cref="GeometryCollection"/>.
        /// </summary>
        /// <param name="geoms">GeometryCollection to transform</param>
        /// <param name="transform">MathTransform</param>
        /// <returns>Transformed GeometryCollection</returns>
        public static IGeometryCollection TransformGeometryCollection(IGeometryCollection geoms,
                                                                      IMathTransform transform,
                                                                      IGeometryFactory geometryFactory)
        {
            return geometryFactory.CreateGeometryCollection(Processor.Transform<IGeometry>(geoms,
                                                                                            o =>
                                                                                            TransformGeometry(o,
                                                                                                              transform,
                                                                                                              geometryFactory)));
        }
    }
}